package ex1;

import java.util.ArrayList;

public class Worker extends Thread{
	final int cellAlive = 1;
	final int cellDead  = 0;
	private Synchronizer mySync;
	private Cell privateCells[][];
	private int lifeTime;
	private ArrayList<ArrayList<Cell>> commonCells;
	private int xCord;
	private int yCord;
	private int cellsDone = 0;
	private int cellsToDo;
	private boolean timeToExit;
	
	public Worker(boolean _area[][], ArrayList<ArrayList<Cell>> _commonCells, int _xCord, int _yCord, int _lifeTime, Synchronizer _sync){
		mySync = _sync;
		privateCells = createArea(_area);
		lifeTime = _lifeTime;
		commonCells = _commonCells;
		xCord = _xCord;
		yCord = _yCord;
		cellsToDo = privateCells.length * privateCells[0].length;
	}

	/**
	 * Creates array of cells from array of boolean.
	 * @param _area - 2D array of boolean.
	 * @return
	 */
	private Cell[][] createArea(boolean[][] _area) {
		Cell newArea[][] = new Cell[_area.length][_area[0].length];
		for(int x=0; x< _area.length; x++){
			for(int y=0; y<_area[0].length; y++){
				newArea[x][y] =  new Cell(_area[x][y]==true ? 1 : 0);
			}
		}
		return newArea;
	}
	
	
	/**
	 * Updates cell in private and common areas if it is possible.
	 * @param xCoordinat
	 * @param yCoordinat
	 * @return TODO
	 */
	private boolean updateCell(int xCoordinat, int yCoordinat){
		boolean update = updatePrivateCell(xCoordinat, yCoordinat);
		if(update){
			updateCommonCell(xCoordinat, yCoordinat);
		}
		if(cellsDone == cellsToDo){
			//All cells are done for good:)
			timeToExit = true;
		}
		return update;
	}
	
	/**
	 * Updates the cell in common area from private area.
	 * @param x - xCoordinat of Cell in private area.
	 * @param y - yCoordinat of Cell in private area.
	 */
	private void updateCommonCell(int x, int y) {
		int currVal = privateCells[x][y].getCurrValue();
		if(commonCells.get(getCommonX(x)).get(getCommonY(y)) != null){
			commonCells.get(getCommonX(x)).get(getCommonY(y)).updateGeneration(currVal);
		}
	}

	/**
	 * Tries to update a cell in  the private area of the worker.
	 * @param xCord - coordinate of the cell in private area.
	 * @param yCord - coordinate of the cell in private area.
	 * @return True if update succeed, false in other case.
	 */
	private boolean updatePrivateCell(int xCord, int yCord){
		int currGen = privateCells[xCord][yCord].getGeneration();
		if(currGen == lifeTime){
			//The end of times.
			return false;
		}
		int currVal = privateCells[xCord][yCord].getCurrValue();
		int nAlive = 0;
		Cell neighboor;
		//count neighbors alive
		for(int x=xCord-1; x<= xCord+1; x++){
			for(int y=yCord-1; y<=yCord+1; y++){
				if((x==xCord) && (y==yCord)) continue; //myself
				neighboor = getCell(x, y);
				if(neighboor.getValue(currGen) == -1){
					//can't update cell because neighbor's generation is not up to date.
					return false;
				}
				if(neighboor.getValue(currGen) == cellAlive){
					nAlive++;
				}
			}
		}
		//we can update the cell
		privateCells[xCord][yCord].updateGeneration(calcState(currVal, nAlive));
		if(privateCells[xCord][yCord].getGeneration() == lifeTime){
			//Done calculating the cell.
			cellsDone++;
		}
		return true;
	}
	
	/**
	 * Implements rule of life.
	 * @param presSate
	 * @param nAlive
	 * @return next state of cell.
	 */
	private int calcState(int presSate, int nAlive){
		if((presSate == cellAlive) && (nAlive == 2 || nAlive == 3)){
			return cellAlive;
		}
		if((presSate == cellDead) && (nAlive == 3)){
			return cellAlive;
		} 
		return cellDead;
	}

	/**
	 * Returns copy of Cell from given coordinates.
	 * @param xCord - x coordinate in private area.
	 * @param yCord - y coordinate in private area.
	 * @return copy of the cell.
	 */
	private Cell getCell(int xCord, int yCord) {
		Cell result;
		if(xCord>=0 && xCord <privateCells.length && yCord>=0 && yCord<privateCells[0].length){
			//cell in the private area
			result = new Cell(privateCells[xCord][yCord]);
		}else{
			//cell in common area			
			if(commonCells.get(getCommonX(xCord)).get(getCommonY(yCord)).isZeroCell() == false){
				result = new Cell(commonCells.get(getCommonX(xCord)).get(getCommonY(yCord))); //reading from common area.
			}else{
				result = new ZeroCell();
			}
		}
		return result;
	}

	/**
	 * Transforms private coord1inat into common coordinate.
	 * @param _x
	 * @return
	 */
	private int getCommonX(int _x){
		return _x + xCord + 1;
	}
	
	/**
	 * Transforms private coord1inat into common coordinate.
	 * @param _y
	 * @return
	 */
	private int getCommonY(int _y){
		return _y + yCord + 1;
	}

	/**
	 * Overrides Thread.run()
	 */
	public void run(){
		for (;;){
			boolean update = false;
			for(int x=0; x< privateCells.length; x++){
				for(int y=0; y<privateCells[0].length; y++){
					update = updateCell(x,y)  || update;
				}
			}
			if(timeToExit){
				return;
			}
			if(update == false){
				//Nothing to do.
				mySync.doWait();
			}
		}
	}
	
	/**
	 * Return result area of life
	 * @return boolean[][] - area.
	 */
	public boolean[][] returnResult(){
		boolean newArea[][] = new boolean[privateCells.length][privateCells[0].length];
		for(int x=0; x< privateCells.length; x++){
			for(int y=0; y<privateCells[0].length; y++){
				newArea[x][y] = (privateCells[x][y].getCurrValue() == cellAlive);
			}
		}
		return newArea;
	}

}
